/* system includes */
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>

#include "cache.h"

#define HASH_FUNCTION(buf,len,seed,out) MurmurHash3_x86_32(buf, len, seed,out)

#define LL_CACHE_SET_ERRNO(ptr,err) \
  do {                              \
    if (ptr)                        \
      *ptr = err;                   \
  } while (0);

#ifdef CACHENDEBUG
#define LL_CACHE_DEBUG(fmt, ...)
#else
#include <stdio.h>
#define LL_CACHE_DEBUG(fmt, ...) \
  fprintf (stderr, fmt, ## __VA_ARGS__);
#endif

//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------

static uint32_t ll_cache_hash(ll_cache_t * cache, char *key)
{
	int len;
	uint32_t hash;

	assert(cache);
	assert(key);

	len = strlen(key);
	HASH_FUNCTION(key, len, cache->seed, &hash);
	hash = hash % cache->max_values;

	return hash;
}

ll_cache_t *ll_cache_create(unsigned int max_values, unsigned int max_size, int *err)
{
	ll_cache_t *cache;
	unsigned int cnt, ecnt;
	ll_cache_entry_t *entry;

	cache = NULL;

	if (max_values != 0) {
		if (max_values > LL_CACHE_DEFAULT_MAX_VALUES) {
			LL_CACHE_SET_ERRNO(err, EINVAL);
			max_values = LL_CACHE_DEFAULT_MAX_VALUES;
		}
	} else {
		max_values = LL_CACHE_MIN_VALUES;
	}

	if (max_size != 0) {
		if (max_size > LL_CACHE_DEFAULT_MAX_SIZE) {
			LL_CACHE_SET_ERRNO(err, EINVAL);
			max_size = LL_CACHE_DEFAULT_MAX_SIZE;
		}
	} else {
		max_size = LL_CACHE_MIN_SIZE;
	}

	if (!(cache = calloc(1, sizeof(ll_cache_t)))) {
		LL_CACHE_SET_ERRNO(err, errno);
		goto error;
	}

	cache->mtime = time(NULL);
	cache->seed = cache->mtime;
	cache->max_values = max_values;
	cache->max_size = max_size;
#ifdef  _PTHREAD_POOL_H
    cache->cache_pool = calloc(1,sizeof(struct pthreadpool_t));
	tpool_init(cache->cache_pool,LL_CACHE_MIN_SIZE/8, LL_CACHE_MIN_VALUES);
#endif

	if (!(cache->values = calloc(cache->max_values, sizeof(ll_cache_entry_t *)))) {
		LL_CACHE_SET_ERRNO(err, errno);
		goto error;
	} else {

		cache->entry_lock = calloc(cache->max_values, sizeof(pthread_rwlock_t));
		cache->value_cnt = calloc(cache->max_values, sizeof(unsigned int));

	}

	for (cnt = 0; cnt < cache->max_values; cnt++) {

		if (pthread_rwlock_init(&cache->entry_lock[cnt], NULL) != 0) {
			LL_CACHE_DEBUG("Error %s: cnt : %u\n", __func__, cnt);
			LL_CACHE_SET_ERRNO(err, errno);
		}

		cache->values[cnt] = calloc(cache->max_size, sizeof(ll_cache_entry_t));
		cache->value_cnt[cnt] = 0;
		for (ecnt = 0; ecnt < cache->max_size; ecnt++) {
			entry = (ll_cache_entry_t *) & cache->values[ecnt];
			entry->key = NULL;
			entry->value = NULL;
		}

	}

	return cache;
 error:
	/* clean up */
	if (cache) {
		if (cache->values)
			free(cache->values);
		if (cache->entry_lock)
			free(cache->entry_lock);
		if (cache->value_cnt)
			free(cache->value_cnt);

		free(cache);
	}
	return NULL;
}

int ll_cache_destroy(ll_cache_t * cache, int flags, int *err)
{
	int ret;
	ll_cache_entry_t *entry, *chain;
	unsigned int cnt, encnt;

	if (!(flags & LL_CACHE_FLAG_DONT_FREE))
		assert(cache->destroy_func);

	if (cache) {
		if (cache->values) {
			for (cnt = 0; cnt < cache->max_values; cnt++) {
				chain = (ll_cache_entry_t *) cache->values[cnt];

				for (encnt = 0; encnt < cache->value_cnt[cnt]; encnt++) {
					entry = (ll_cache_entry_t *) & chain[encnt];

					if (entry->key == NULL)
						continue;
					LL_CACHE_DEBUG("D %s: key: %s, cnt %u: %u,%u\n", __func__,
										(char *)entry->key, cache->value_cnt[cnt], cnt, encnt);

					if (entry->value != NULL)
						cache->destroy_func(entry->value);

					if (entry->key)
						free(entry->key);
				}

				free(cache->values[cnt]);
				if ((ret = pthread_rwlock_destroy(&cache->entry_lock[cnt])) != 0) {
					LL_CACHE_SET_ERRNO(err, ret);
				}
			}
			free(cache->values);
			free(cache->value_cnt);
		}

		free(cache);
	}

	return 0;
}

void *ll_cache_get(ll_cache_t * cache, char *key, int flags, int *err)
{
	int ret, cnt;
	ll_cache_entry_t *chain, *entry;
	time_t now;
	uint32_t hash;
	void *retval;

	assert(cache);
	assert(key);

	hash = ll_cache_hash(cache, key);
	chain = (ll_cache_entry_t *) cache->values[hash];

	do {

		if ((ret = pthread_rwlock_rdlock(&cache->entry_lock[hash])) != 0) {
			LL_CACHE_SET_ERRNO(err, ret);
			retval = NULL;
			break;
		}

		retval = entry = NULL;
		if (chain != NULL) {
			now = time(NULL);
			for (cnt = 0; cnt < cache->max_size; cnt++) {

				entry = (ll_cache_entry_t *) & chain[cnt];

				if (entry->key == NULL)
					break;
				if (strncmp(entry->key, key, strlen(key)) == 0) {
					/* only return cache entry if it is valid */
					if (entry->mtime < cache->mtime || (entry->mtime + cache->ttl) < now)
					   retval = NULL;
					else 
					   retval = entry->value;

					break;
				}
			}
		}

		if (entry != NULL)
			LL_CACHE_DEBUG("%u: key: %s, hash: %s, %u mtime: %u, ttl: %u\n",
					cache->value_cnt[hash], entry->key, (char *)entry->value, hash, (unsigned int)entry->mtime,
					cache->ttl);

		if ((ret = pthread_rwlock_unlock(&cache->entry_lock[hash])) != 0) {
			LL_CACHE_SET_ERRNO(err, ret);
			retval = NULL;
			break;
		}

	} while (0);

	return (retval);
}

typedef struct XPTH_PARAM {
    unsigned int size;
    void *chain;
} xpth_param;

void xchain_pthread(void *p) {

    xpth_param *xpm;
#ifdef  _PTHREAD_POOL_H
    xpm = (xpth_param *)p;
	cache->xchain_func((ll_cache_entry_t *)xpm->chain,xpm->size);
#endif
    free(xpm);
    return;
}

int ll_cache_set(ll_cache_t * cache, char *key, void *value, int flags, int *err)
{
	int res, ret, cnt;
	int prop_flags;				  /* propagation flags */
	ll_cache_entry_t *chain, *cur, *entry;
	size_t resize, size;
	time_t now;
	uint32_t hash;

	assert(cache);
	assert(key);
	assert(value);

	hash = ll_cache_hash(cache, key);
	chain = (ll_cache_entry_t *) cache->values[hash];

	if ((ret = pthread_rwlock_wrlock(&cache->entry_lock[hash])) != 0) {
		LL_CACHE_SET_ERRNO(err, ret);
		return -1;
	}

	res = 0;
	prop_flags = flags | LL_CACHE_FLAG_DONT_LOCK;

	do {
		entry = NULL;
		if (chain != NULL) {
			for (cnt = 0; cnt < cache->max_size; cnt++) {

				entry = (ll_cache_entry_t *) & chain[cnt];

				if (entry->key == NULL)
					break;			  //NotFound

				if (strncmp(entry->key, key, strlen(key)) == 0) {
					if ( (entry->value != NULL) && (cache->destroy_func) )
						cache->destroy_func(entry->value);
                    entry->mtime = time(NULL);
                    entry->access = 1;
					entry->key = key;
					entry->value = value;

					LL_CACHE_DEBUG("S0 %s: key: %s, hash: %u size %u\n", entry->key, key, hash, cache->value_cnt[hash]);
					break;
				}
			}
		}

	} while (0);

	/* remove unwanted hash table entries so we stay within bounds */
	if (cnt >= cache->max_size) {
		//release_overflow max_size;
	LL_CACHE_DEBUG("over size %s: key: %s, hash: %u size %u\n", __func__, key, hash, cache->value_cnt[hash]);

	if( cache->xchain_func != NULL ) {
        cache->xchain_func(chain,cache->max_size);
        ll_cache_set(cache,key,value,flags,err);
#ifdef  _PTHREAD_POOL_H
     if( cache->cache_pool != NULL ) {
            int xi;
            xpth_param *xpm = calloc( 1, sizeof(xpth_param)) ;
             
            for ( xi = 0 ; xi < cache->max_values; xi++ )  {
            xpm->size = cache->max_size;
            xpm->chain = (ll_cache_entry_t *)cache->values[xi];
		    cache->cache_pool->tasklist.task_add(&cache->cache_pool->tasklist, xchain_pthread,xpm);
            }
        } 
#endif
      }

	} else {

		LL_CACHE_DEBUG("S1 %s: key: %s, hash: %u, seting %d\n", __func__, key, hash, cache->value_cnt[hash]);

		if (entry->key == NULL) {
			entry->mtime = time(NULL);
			entry->access = 1;
			entry->key = key;
			entry->value = value;
			cache->value_cnt[hash] += 1;
			LL_CACHE_DEBUG("S1 %s: key: %s, hash: %u size %u\n", entry->key, key, hash, cache->value_cnt[hash]);
		}

	}

 unlock:
	entry = (ll_cache_entry_t *) cache->values[hash];
	if ((ret = pthread_rwlock_unlock(&cache->entry_lock[hash])) != 0) {
		LL_CACHE_SET_ERRNO(err, ret);
		res = ret;
	}

	return res;
 error:
	res = -1;
	goto unlock;
}

int ll_cache_unset(ll_cache_t * cache, char *key, int flags, int *err)
{
	int len, cnt, res, ret;
	ll_cache_entry_t *chain, *entry;
	uint32_t hash;

	assert(cache);
	assert(key);

	res = 0;

	len = strlen(key);
	hash = ll_cache_hash(cache, key);
	chain = (ll_cache_entry_t *) cache->values[hash];

	do {

		if ((ret = pthread_rwlock_wrlock(&cache->entry_lock[hash])) < 0) {
			LL_CACHE_SET_ERRNO(err, ret);
			res = -1;
			break;
		}

		for (cnt = 0; cnt < cache->max_size; cnt++) {
			entry = (ll_cache_entry_t *) & chain[cnt];

			if (entry->key == NULL)
				break;				  //NotFound
			if (strncmp(entry->key, key, strlen(key)) == 0) {
				if ( (entry->value != NULL) && ( cache->destroy_func) )
					cache->destroy_func(entry->value);
				entry->key = NULL;
				entry->value = NULL;
				if (--cache->value_cnt[hash] < 0)
					cache->value_cnt[hash] = 0;

			}
		}

		if ((ret = pthread_rwlock_unlock(&cache->entry_lock[hash])) != 0) {
			LL_CACHE_SET_ERRNO(err, res);
			break;
		}

	} while (0);

	return res;

}
